Skip to content

Chromatic: snap pane splits to final geometry instead of tweening#235

Merged
nedtwigg merged 2 commits into
mainfrom
chromatic-stability
Jul 9, 2026
Merged

Chromatic: snap pane splits to final geometry instead of tweening#235
nedtwigg merged 2 commits into
mainfrom
chromatic-stability

Conversation

@nedtwigg

@nedtwigg nedtwigg commented Jul 9, 2026

Copy link
Copy Markdown
Member

Symptom

Terminal stories that split panes (Wall: Multi Pane, WithDoors) intermittently snapshot a pane whose last prompt line is clipped — user@dormouse:~$ rendered as user@do.

Root cause

The split runs Lath's 440ms geometry tween, resizing panes through many transient widths. xterm's DOM renderer can latch onto a frame where a pane is still narrow and freeze its last line clipped to that width — even after the layout settles to full width. It's a rendering race on the animation, not a content or column-count problem: the terminal buffer is correct the whole time, only the painted DOM is stale.

This is why the first attempt in this PR (gating the snapshot on "settled grid geometry") didn't help — it targeted column width, but the clipped frame is a renderer artifact of the tween, independent of the final cols.

Fix

Disable Lath layout motion under Chromatic (cfg.layout.animate), exactly the way preview.ts already freezes marching ants, the cursor blink, and the alert ring for deterministic snapshots. Splits / restores / kills snap straight to their final geometry, so there's no transient-width frame for the renderer to latch onto.

  • The animator already collapses to instant at durationMs: 0 (the existing reduced-motion path), so this just routes Chromatic through it — no new animation code.
  • Chromatic captures the settled state after each story's play, which is identical with or without the tween. No story intentionally captures a mid-animation frame, so there's no visual change to any snapshot.

settle-terminals.ts is reverted to its original content-only gate (the superseded geometry approach is removed).

Files

  • lib/src/cfg.ts — add layout.animate flag
  • lib/src/components/wall/lath-wall-engine.ts — collapse tween to instant when !cfg.layout.animate
  • lib/.storybook/preview.ts — set cfg.layout.animate = false under the Chromatic UA

Validation

Reproduced and measured with a headless-but-visible Playwright harness that loads the story in fresh pages (mirroring how Chromatic renders), over the Wall: Multi Pane story:

build flake rate
baseline (tween on) 30% (9/30), symptom user@do reproduced exactly
this fix (instant) 0/150

Also confirmed via component isolation that the animation is the sole cause: instant-motion alone → 0%, while keeping the tween on with other mitigations → still ~25%.

Lib typecheck + full unit suite (1045 tests) green.

🤖 Generated with Claude Code

…not just content

The terminal-story readiness gate waited only for text to reach the xterm
buffer, which happens almost instantly (flattenScenario writes the whole
prompt in one go). It never waited for the terminal's column count to settle.

But a split kicks off a Lath tween that animates each pane's real width across
many frames (LathHost sets el.style.width per rAF), and TerminalPane's refit is
throttled with a 150ms trailing edge, so cols keeps reflowing well after the
content lands. settleTerminals resolved ~2 paint frames after content appeared,
so Chromatic sometimes snapshotted a pane still laid out at a transitional
width -- `user@dormouse:~$` clipped to `user@do`. Intermittent because the
two-frame cushion sometimes outlasted the throttle and sometimes didn't.

Gate on content AND settled geometry: each poll, force every live terminal to
fit its current resting container (bypassing the 150ms throttle so we read the
true resting cols), and require the grid to hold steady for STABLE_POLLS
consecutive polls before releasing the snapshot. Also await document.fonts.ready
first (bounded) since cell metrics are measured from the font. Public API
(settleTerminals / waitForCondition) is unchanged, so all consumer stories are
unaffected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 9, 2026

Copy link
Copy Markdown

Deploying mouseterm with  Cloudflare Pages  Cloudflare Pages

Latest commit: 84a0902
Status: ✅  Deploy successful!
Preview URL: https://3f4f3b80.mouseterm.pages.dev
Branch Preview URL: https://chromatic-stability.mouseterm.pages.dev

View logs

Terminal stories that split panes (Wall: Multi Pane, WithDoors) intermittently
snapshotted a pane with its last prompt line clipped — `user@dormouse:~$`
rendered as `user@do`.

Root cause: the split runs Lath's 440ms geometry tween, resizing panes through
many transient widths. xterm's DOM renderer can latch a frame where a pane is
still narrow and freeze its last line clipped to that width even after the
layout settles to full width. It's a rendering race on the animation, not a
content or column-count problem — the buffer is correct throughout.

Fix: disable Lath layout motion under Chromatic (cfg.layout.animate), the same
way preview.ts already freezes marching ants, the cursor blink, and the alert
ring for deterministic snapshots. Splits/restores/kills snap straight to their
final geometry, so no transient-width frame exists for the renderer to latch.
The animator already collapses to instant at durationMs 0 (the reduced-motion
path), so this reuses that code path. Snapshots capture the settled state, which
is identical with or without the tween — no visual change to any story.

This supersedes the earlier settle-terminals "settled grid geometry" gate, which
targeted column width and did not address the rendering race; settle-terminals
is reverted here to its original content-only form.

Validated with a headless-visible Playwright harness (mirrors Chromatic) over
the Wall: Multi Pane story: 30% truncation baseline → 0/150 with this change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@nedtwigg nedtwigg changed the title settle-terminals: gate Chromatic snapshots on settled grid geometry Chromatic: snap pane splits to final geometry instead of tweening Jul 9, 2026
@nedtwigg nedtwigg merged commit 05bb639 into main Jul 9, 2026
9 checks passed
@nedtwigg nedtwigg deleted the chromatic-stability branch July 9, 2026 05:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants